package top.saplf.sileny.reader.shelf

import com.github.ajalt.timberkt.Timber
import com.hannesdorfmann.mosby3.mvi.MviBasePresenter
import com.hannesdorfmann.mosby3.mvp.MvpView
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers

/**
 * @author saplf
 */
interface LocalBookIndexView : MvpView {
    fun loadFirstPageIntent(): Observable<Boolean>

    fun loadNextPageIntent(): Observable<LocalBook>

    fun pullToRefreshIntent(): Observable<Boolean>

    fun render(viewState: LocalBookIndexViewState)
}

class LocalBookIndexPresenter(private val loader: LocalBookLoader)
    : MviBasePresenter<LocalBookIndexView, LocalBookIndexViewState>() {

    override fun bindIntents() {
        val firstPage: Observable<PartialStateChanges> =
                intent(LocalBookIndexView::loadFirstPageIntent)
                        .doOnNext { Timber.d { "intent: load first page" } }
                        .flatMap {
                            loader.loadFirstPage()
                                    .map(::FirstPageLoaded)
                                    .cast(PartialStateChanges::class.java)
                                    .startWith(FirstPageLoading())
                                    .onErrorReturn(::FirstPageError)
                                    .subscribeOn(Schedulers.io())
                        }

        val nextPage: Observable<PartialStateChanges> =
                intent(LocalBookIndexView::loadNextPageIntent)
                        .doOnNext { Timber.d { "intent: load next page" } }
                        .flatMap {
                            loader.loadNextPage(it)
                                    .map(::NextPageLoaded)
                                    .cast(PartialStateChanges::class.java)
                                    .startWith(NextPageLoading())
                                    .onErrorReturn { NextPageError(it) }
                                    .subscribeOn(Schedulers.io())
                        }

        val refresh: Observable<PartialStateChanges> =
                intent(LocalBookIndexView::pullToRefreshIntent)
                        .doOnNext { Timber.d { "intent: pull to refresh" } }
                        .flatMap {
                            loader.loadRefreshData()
                                    .map(::PullToRefreshLoaded)
                                    .cast(PartialStateChanges::class.java)
                                    .startWith(PullToRefreshLoading())
                                    .onErrorReturn { PullToRefreshError(it) }
                                    .subscribeOn(Schedulers.io())
                        }

        val allIntent = Observable.merge(firstPage, nextPage, refresh)
                .observeOn(AndroidSchedulers.mainThread())

        val initialState = FirstPageLoadingViewState(emptyList())

        subscribeViewState(
                allIntent.scan(initialState, this::viewStateReducer),
                LocalBookIndexView::render
        )
    }

    private fun viewStateReducer(
            previousState: LocalBookIndexViewState,
            partialStateChanges: PartialStateChanges
    ): LocalBookIndexViewState = when (partialStateChanges) {
        is FirstPageLoading -> FirstPageLoadingViewState(previousState.data)
        is FirstPageError -> FirstPageErrorViewState(previousState.data, partialStateChanges.error)
        is FirstPageLoaded -> ReloadDataViewState(partialStateChanges.data)
        is NextPageLoading -> NextPageLoadingViewState(previousState.data)
        is NextPageError -> NextPageErrorViewState(previousState.data, partialStateChanges.error)
        is NextPageLoaded -> AppendDataViewState(partialStateChanges.data)
        is PullToRefreshLoading -> PullToRefreshingViewState(previousState.data)
        is PullToRefreshError -> PullToRefreshErrorViewState(previousState.data, partialStateChanges.error)
        is PullToRefreshLoaded -> ReloadDataViewState(partialStateChanges.data)
    }

    override fun unbindIntents() {
        loader.close()
    }

}

sealed class LocalBookIndexViewState(
        val data: List<LocalBook>
)

class FirstPageLoadingViewState(
        data: List<LocalBook>
) : LocalBookIndexViewState(data)

class FirstPageErrorViewState(
        data: List<LocalBook>,
        val error: Throwable
) : LocalBookIndexViewState(data)

class ReloadDataViewState(
        data: List<LocalBook>
) : LocalBookIndexViewState(data)

class NextPageLoadingViewState(
        data: List<LocalBook>
) : LocalBookIndexViewState(data)

class NextPageErrorViewState(
        data: List<LocalBook>,
        val error: Throwable
) : LocalBookIndexViewState(data)

class AppendDataViewState(
        data: List<LocalBook>
) : LocalBookIndexViewState(data)

class PullToRefreshingViewState(
        data: List<LocalBook>
) : LocalBookIndexViewState(data)

class PullToRefreshErrorViewState(
        data: List<LocalBook>,
        val error: Throwable
) : LocalBookIndexViewState(data)
